import ctypes
import socket
import time

from musicpy import *


def OSCMessage(*lst):
    data = b""
    for e in lst:
        t = type(e)
        if t is bytes:
            d = e
            d += b"\0" * (4 - len(d) % 4)
        elif t is str:
            d = e.encode("utf-8")
            d += b"\0" * (4 - len(d) % 4)
        elif t is int:
            d = struct.pack(">i", e)
        elif t is float:
            d = struct.pack(">f", e)
        data += d
    return data


class ReaperRemote:
    def __init__(self, command_size=10240, data_size=10240):
        self.remote_control_id = "_RSbca5a9727ffaa980435fc104cd6d45339a63e7d8"
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.wake()
        # print('已通过OSC (TCP/IP)方式连接到Reaper')

        self.command_size = command_size
        self.data_size = data_size
        self.lib = ctypes.cdll.LoadLibrary(
            r"B:\codes\C_Cpp\DMuse\ReaperRemoteMMAP\cmake-build-debug\libReaperRemoteMMAP.dll")
        self.command_tagname, self.data_tagname = b'rpr_remote_0', b'rpr_remote_1'
        self.lib.Listen(ctypes.c_char_p(self.command_tagname), ctypes.c_char_p(self.data_tagname), self.command_size,
                        self.data_size)
        # print('已通过MMAP方式连接到Reaper')

    def close(self):
        self.socket.close()

    def send_command(self, s):
        self.lib.Clear(0)
        b2 = s.encode('utf-8')
        # print('send:', b2, len(b2))
        self.lib.PushMsg(b2, 0, len(b2))

    def recv_data(self):
        s = b''
        p = ctypes.c_char_p(s)
        self.lib.GetMsg(p, 1)
        s2 = str(p.value, encoding='utf-8')
        self.lib.Clear(1)
        # print('recv:', chan, p.value)
        return s2

    def fetch(self, command):
        self.send_command(command)
        r = ''
        while r == '':
            r = self.recv_data()
            time.sleep(0.02)
        return r

    def wake(self):
        self.socket.sendto(OSCMessage("/action/str", ",i", 41898), ('127.0.0.1', 8000))
        self.socket.sendto(OSCMessage("/action/str", ",s", self.remote_control_id), ('127.0.0.1', 8000))

    def set_params(self, params, track_id=-1, fx_id=-1):
        params_ = '(' + ','.join(["%.3f" % params[i] for i in range(len(params))]) + ')'
        if track_id == -1:
            track_ = "RPR_GetTrack(0,RPR_GetNumTracks()-1)"
        else:
            track_ = f"RPR_GetTrack(0,{track_id})"
        if fx_id == -1:
            fx_ = f"RPR_TrackFX_GetCount({track_})-1"
        else:
            fx_ = str(fx_id)
        '''command = f"[RPR_TrackFX_SetParamNormalized({track_}," + \
                    f" RPR_GetFocusedFX2(int(RPR_GetMediaTrackInfo_Value(RPR_GetTrack(0,RPR_GetNumTracks()-1)," + \
                    f" 'IP_TRACKNUMBER')),0,0)[3],i,j) for i,j in enumerate({params_})]"'''
        command = f"[RPR_TrackFX_SetParamNormalized({track_},{fx_},i,j) for i,j in enumerate({params_})]"
        # print(command)
        self.send_command(command)

    def create_midi_item(self, beats_from, beats_to, track_id=-1, new_track=False, no_new=False):
        if new_track:
            self.insert_track(-1)
            track_ = "RPR_GetTrack(0,RPR_GetNumTracks()-1)"
        else:
            if track_id == -1:
                track_ = "RPR_GetTrack(0,RPR_GetNumTracks()-1)"
            else:
                track_ = f"RPR_GetTrack(0,{track_id})"
        if no_new:
            no_new_ = f" if RPR_CountTrackMediaItems({track_})==0 else None"
        else:
            no_new_ = ""
        command = f"RPR_CreateNewMIDIItemInProj({track_}," + \
                  f" {'%.3f' % beats_from}/RPR_Master_GetTempo()*240," + \
                  f" {'%.3f' % beats_to}/RPR_Master_GetTempo()*240,False){no_new_}"
        self.fetch(command)

    def set_notes(self, chord_, octave=0, chan=0, track_id=-1):
        time_ = chord_.start_time
        data_list = []
        for i, interval_ in zip(range(len(chord_)), chord_.interval):
            data_list.append(
                f'({time_},{time_ + chord_[i].duration * 4},{chan},{octave * 12 + chord_[i].degree},{chord_[i].volume})')
            time_ += interval_ * 4
        data_ = '(' + ','.join(data_list) + ')'

        if track_id == -1:
            track_ = "RPR_GetTrack(0,RPR_GetNumTracks()-1)"
        else:
            track_ = f"RPR_GetTrack(0,{track_id})"
        command = f"[RPR_MIDI_InsertNote(RPR_GetActiveTake(RPR_GetTrackMediaItem({track_}, 0))," + \
                  f" True, False, RPR_MIDI_GetPPQPosFromProjQN(RPR_GetActiveTake(RPR_GetTrackMediaItem({track_}, 0)), d[0])," + \
                  f"RPR_MIDI_GetPPQPosFromProjQN(RPR_GetActiveTake(RPR_GetTrackMediaItem({track_}, 0)), d[1])," + \
                  f" d[2], d[3], d[4], True) for d in {data_}]"
        self.fetch(command)
        command = f"RPR_MIDI_Sort(RPR_GetActiveTake(RPR_GetTrackMediaItem(RPR_GetTrack(0,RPR_GetNumTracks()-1), 0)))"
        self.fetch(command)

    def delete_first_item(self):
        command = f"RPR_DeleteTrackMediaItem(RPR_GetTrack(0,RPR_GetNumTracks()-1), RPR_GetTrackMediaItem(RPR_GetLastTouchedTrack(), 0))"
        self.fetch(command)

    def insert_track(self, index=-1):
        if index == -1:
            command = f"RPR_InsertTrackAtIndex(RPR_GetNumTracks(), True)"
        else:
            command = f"RPR_InsertTrackAtIndex({index}, True)"
        self.fetch(command)

    def add_trackfx(self, track_id=-1, fxname=''):
        if track_id == -1:
            command = f"RPR_TrackFX_AddByName(RPR_GetTrack(0,RPR_GetNumTracks()-1), \"{fxname}\", False, 1)"
        else:
            command = f"RPR_TrackFX_AddByName(RPR_GetTrack(0,{track_id}), \"{fxname}\", False, 1)"
        self.fetch(command)

    def select_last_track(self):
        command = f"RPR_SetTrackSelected(RPR_GetNumTracks()-1, True)"
        self.fetch(command)

    def add_tracks(self, n_tracks):
        command = f"[RPR_InsertTrackAtIndex(RPR_GetNumTracks(),True) for i in range({n_tracks})]"
        self.fetch(command)

    def delete_tracks(self, n_tracks):
        command = f"[RPR_DeleteTrack(RPR_GetTrack(0,0)) for i in range({n_tracks})]"
        self.fetch(command)

    def set_bpm(self, bpm):
        command = f"RPR_SetCurrentBPM(0,{bpm},False)"
        self.fetch(command)

    def play(self):
        self.socket.sendto(OSCMessage("/action/str", ",i", 1007), ('127.0.0.1', 8000))

    def render(self):
        self.socket.sendto(OSCMessage("/action/str", ",i", 42230), ('127.0.0.1', 8000))


def get_files(directory, ext, with_dot=True):
    path_list = []
    for paths in [[os.path.join(dirpath, name).replace('/', '\\') for name in filenames if
                   name.endswith(('.' if with_dot else '') + ext)] for
                  dirpath, dirnames, filenames in os.walk(directory)]:
        path_list.extend(paths)
    return path_list


if __name__ == '__main__':
    ser = ReaperRemote()
    for _ in range(1):
        '''ser.wake()
        ser.insert_track(index=0)

        ser.wake()
        ser.add_trackfx(fxname="Sylenth1 (LennarDigital)")

        fxp_files = get_files(r"B:\muse_repo\Presets", 'fxp')
        preset = random.choice(fxp_files)
        engine = dmr.RenderEngine(44100, 256)
        instr = engine.make_plugin_processor('instr_1', 'C:/VST/64bit/Sylenth1.dll')
        # instr = engine.make_plugin_processor('instr_1', r"C:\VST\64bit\Massive.dll")
        engine.load_graph([(instr, [])])
        instr.load_preset(preset)
        params = [instr.get_parameter(i) for i in range(instr.get_plugin_parameter_size())]
        print(params)

        ser.wake()
        ser.delete_first_item()

        ser.wake()
        ser.create_midi_item(0.0, 4.0)

        mid_files = get_files(r"B:\muse_repo\MIDI\chord", 'mid')
        midi = random.choice(mid_files)
        piece = read(midi)
        chord1 = piece.tracks[0]
        ser.wake()
        ser.set_notes(chord1)

        ser.wake()
        ser.set_params(params)

        ser.play()
        time.sleep(4)'''

        '''fxp_files = get_files(r"B:\muse_repo\Presets\Sylenth1\bass", 'fxp')
        preset = random.choice(fxp_files)
        engine = dmr.RenderEngine(44100, 256)
        instr = engine.make_plugin_processor('instr_1', 'C:/VST/64bit/Sylenth1.dll')
        engine.load_graph([(instr, [])])
        instr.load_preset(preset)
        params = [instr.get_parameter(i) for i in range(instr.get_plugin_parameter_size())]
        ser.wake()
        ser.set_params(params)'''

    ser.close()
